{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Delay Blind Approach -- Scenario II\n",
    "\n",
    "In this notebook, we report the code related to the *delay-blind* approach in our paper [[1]](#ourpaper). The code trains a neural network to map the CSI to the probability of an error event for all the MCSs available at the base station and select the MSC that maximizes the spectral efficiency. We assume that the outdated CSI available at the base station is actually the instantaneous CSI, therefore no prediction is performed. In our paper [[1]](#ourpaper), we use this setup as a baseline to show the degrading effects of outdated CSI.\n",
    "\n",
    "Under Scenario II, for each feedback delay, we train a single neural network over the full range of signal-to-noise ratios and dopplers.\n",
    "\n",
    "It must be noted that the training datasets listed below in the code are currently not available in the repository due to space limitations. The **training datasets can be found at**: https://kth.box.com/s/tcd7y7rg3yau75kctw3regmyns8kfkr6 in the folder *Datasets*. At any rate, in the repository, the reader can also find the codes in *radio_data* folder which can be run to generate the datasets. \n",
    "\n",
    "**Note**: the training might take some hours, depending on the available computational resources, the dimension of the training set, the dimension of the network, and the number of epochs. \n",
    "\n",
    "\n",
    "<a id='ourpaper'></a> [1] \"Wireless link adaptation - a hybrid data-driven and model-based approach\", Lissy Pellaco, Vidit Saxena, Mats Bengtsson, Joakim Jaldén. Submitted to SPAWC 2020."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Import libraries and utility functions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import time\n",
    "from keras.optimizers import Adam\n",
    "from keras.backend.tensorflow_backend import set_session\n",
    "from keras.backend import clear_session\n",
    "import tensorflow as tf\n",
    "# Utility functions defined un utilities.py\n",
    "import utilities as utils"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Number of subcarriers in the OFDM\n",
    "NROF_SUBCARRIERS = 72\n",
    "# Number of MCSs\n",
    "NROF_MCS = 29\n",
    "# Flag used to indicate if the channel is noisy\n",
    "CHANNEL_EST_NOISE = True\n",
    "# Parameters related to neural network training\n",
    "BATCH_SIZE = 32\n",
    "NROF_EPOCHS = 10\n",
    "TRAINING_FRACTION = 0.2\n",
    "# Flag to indicate if the trained models should be saved\n",
    "save_model = False"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Load the Dataset\n",
    "\n",
    "The channel dataset is a dict with the following keys :  \n",
    " - 'channel'\n",
    "     - Complex channel coefficients \n",
    "     - Numpy array [ NROF_FRAMES x NROF_SUBCARRIERS x NROF_SNRS]\n",
    " - 'block_success'\n",
    "      - Binary success events (ACKs)\n",
    "      - Numpy array [ NROF_FRAMES x NROF_MCS x NROF_SNRS]\n",
    " - 'snrs_db '      \n",
    "     - Evaluated average SNR values\n",
    "     - Numpy array [ NROF_SNRS ]\n",
    " - 'block_sizes'\n",
    "     - Evaluated transport block sizes\n",
    "     - Numpy array [ NROF_MCS ]\n",
    "     \n",
    "The name of the dataset, e.g., ITU_VEHICULAR_B_5000_60kmph, is to be interpreted in this way: \n",
    " - channel model (ITU_VEHICULAR_B)\n",
    " - number of channel realizations (5000)\n",
    " - relative velocity between the base station and the user mobile equipment (60kmph)\n",
    " \n",
    "The **training datasets can be found at**: https://kth.box.com/s/tcd7y7rg3yau75kctw3regmyns8kfkr6 in the folder *Datasets*."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# The files stored in the file_set ARE NOT in the repository due to space limitations.\n",
    "# The training datasets can be found at: https://kth.box.com/s/tcd7y7rg3yau75kctw3regmyns8kfkr6 in the folder *Datasets*\n",
    "# The reader has also access to the \"radio_data/Generate_Data.ipynb\" which we used to generate the training datasets.\n",
    "# N.B. a small training set with new samples at snr of 5dB is added to boost the performance at low snr\n",
    "FADING_CHANNEL_DATAFILES = []\n",
    "\n",
    "FADING_CHANNEL_DATAFILES.append('Datasets/ITU_VEHICULAR_B_5000_30kmph.npy')\n",
    "FADING_CHANNEL_DATAFILES.append('Datasets/ITU_VEHICULAR_B_5000_45kmph.npy')\n",
    "FADING_CHANNEL_DATAFILES.append('Datasets/ITU_VEHICULAR_B_5000_60kmph.npy')\n",
    "FADING_CHANNEL_DATAFILES.append('Datasets/ITU_VEHICULAR_B_5000_75kmph.npy')\n",
    "FADING_CHANNEL_DATAFILES.append('Datasets/ITU_VEHICULAR_B_5000_90kmph.npy')\n",
    "FADING_CHANNEL_DATAFILES.append('Datasets/ITU_VEHICULAR_B_5000_105kmph.npy')\n",
    "FADING_CHANNEL_DATAFILES.append('Datasets/ITU_VEHICULAR_B_5000_120kmph.npy')\n",
    "FADING_CHANNEL_DATAFILES.append('Datasets/ITU_VEHICULAR_B_350_60kmph.npy')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Build the Neural Network model, define the optimizer, and the cost function"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def create_ann_model():\n",
    "    from keras.models import Sequential\n",
    "    from keras.layers import Dense, Dropout\n",
    "\n",
    "    model = Sequential()\n",
    "    model.add( Dense( 1024, \n",
    "                      input_dim = NROF_SUBCARRIERS * 2, \n",
    "                      kernel_initializer='normal', \n",
    "                      activation='relu' ) )\n",
    "\n",
    "    model.add( Dense( 512, \n",
    "                      kernel_initializer = 'normal', \n",
    "                      activation='relu' ) )\n",
    "        \n",
    "    model.add( Dense( 1024, \n",
    "                      kernel_initializer = 'normal', \n",
    "                      activation='relu' ) )\n",
    "    \n",
    "    model.add( Dense( NROF_MCS, \n",
    "                      kernel_initializer='normal', \n",
    "                      activation='sigmoid' ) )\n",
    "\n",
    "    # Compile model\n",
    "    adam = Adam(lr = 0.001, beta_1 = 0.9, beta_2 = 0.999, amsgrad = False)\n",
    "    model.compile(loss = 'binary_crossentropy', optimizer = adam, metrics = ['accuracy'])  # for binary classification\n",
    "    \n",
    "    return model\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Train the Neural Network"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "channel_coeff  = []\n",
    "block_success  = []\n",
    "\n",
    "# Extract training data from all datasets, excluding the last one (used later)\n",
    "for file in FADING_CHANNEL_DATAFILES[:-1]:\n",
    "    DATASET = np.load( file, allow_pickle = True )[()]\n",
    "    \n",
    "    nrof_train_samples = int( TRAINING_FRACTION * DATASET['channel'].shape[0] )\n",
    "    coeff = utils.calculate_channel_coefficients_scaled( DATASET['channel'][ :nrof_train_samples, :, : ],\n",
    "                                                         DATASET['snrs_db'],\n",
    "                                                         channel_estimation_noise = CHANNEL_EST_NOISE )\n",
    "\n",
    "    channel_coeff.append( coeff )\n",
    "    block_success.append( DATASET['block_success'][ :nrof_train_samples, :, : ] )\n",
    "    \n",
    "channel_coeff = np.vstack( channel_coeff )\n",
    "block_success = np.vstack( block_success )\n",
    "\n",
    "NROF_FRAMES, NROF_SUBCARRIERS, NROF_SNRS = channel_coeff.shape\n",
    "NROF_MCS = block_success.shape[ 1 ]\n",
    "\n",
    "channel_coeff_concat = np.concatenate( ( np.real( channel_coeff ), np.imag( channel_coeff ) ), axis = 1 )\n",
    "\n",
    "train_input  = utils.flatten_snr_axis( channel_coeff_concat ) \n",
    "train_target = utils.flatten_snr_axis( block_success )\n",
    "\n",
    "train_input, train_target = utils.shuffle_data( train_input, train_target )\n",
    "\n",
    "config = tf.ConfigProto()\n",
    "config.gpu_options.allow_growth = True  \n",
    "\n",
    "sess = tf.Session( config = config )\n",
    "set_session(sess) \n",
    "\n",
    "\n",
    "model = create_ann_model( )\n",
    "\n",
    "# Storing current time \n",
    "start = time.time()\n",
    "\n",
    "history = model.fit( train_input, \n",
    "                     train_target, \n",
    "                     batch_size = BATCH_SIZE, \n",
    "                     epochs     = NROF_EPOCHS, \n",
    "                     validation_split = 0.1, \n",
    "                     verbose    = 1 ) # the \"verbose parameter\" can be changed to display more about the training progess of each epoch\n",
    "\n",
    "channel_coeff  = []\n",
    "block_success  = []\n",
    "\n",
    "# Extract training data from last dataset\n",
    "for file in [FADING_CHANNEL_DATAFILES[-1]]:\n",
    "    DATASET = np.load( file, allow_pickle = True )[()]\n",
    "    \n",
    "    nrof_train_samples = int( TRAINING_FRACTION * DATASET['channel'].shape[0] )\n",
    "    coeff = utils.calculate_channel_coefficients_scaled( DATASET['channel'][ :nrof_train_samples, :, : ],\n",
    "                                                         DATASET['snrs_db'],\n",
    "                                                         channel_estimation_noise = CHANNEL_EST_NOISE )\n",
    "\n",
    "    channel_coeff.append( coeff )\n",
    "    block_success.append( DATASET['block_success'][ :nrof_train_samples, :, : ] )\n",
    "    \n",
    "channel_coeff = np.vstack( channel_coeff )\n",
    "block_success = np.vstack( block_success )\n",
    "\n",
    "NROF_FRAMES, NROF_SUBCARRIERS, NROF_SNRS = channel_coeff.shape\n",
    "NROF_MCS = block_success.shape[ 1 ]\n",
    "\n",
    "channel_coeff_concat = np.concatenate( ( np.real( channel_coeff ), np.imag( channel_coeff ) ), axis = 1 )\n",
    "\n",
    "train_input  = utils.flatten_snr_axis( channel_coeff_concat ) \n",
    "train_target = utils.flatten_snr_axis( block_success )\n",
    "\n",
    "train_input, train_target = utils.shuffle_data( train_input, train_target )\n",
    "# Additional training\n",
    "history = model.fit( train_input, \n",
    "                     train_target, \n",
    "                     batch_size = BATCH_SIZE, \n",
    "                     epochs     = NROF_EPOCHS, \n",
    "                     validation_split = 0.1, \n",
    "                     verbose    = 1 ) # the \"verbose parameter\" can be changed to display more about the training progess of each epoch\n",
    "\n",
    "\n",
    "file = 'Trained_models_ScenarioII/ANN_MCS_PRED.h5'\n",
    "print(\"Training the neural network took:\",time.time()-start)\n",
    "if save_model == True:\n",
    "    model.save( file )\n",
    "    print( 'Saved model to %s'%( file ) )\n",
    "    \n",
    "                                                   "
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
